SimpleAMM v2 Documentation

Architectural Overview

Core Components

  1. SimpleAMMV2 (Main Contract)

  2. EternalStorage

  3. EmergencyMultiSig

  4. LPToken

  5. Migration Scripts

Core Features

  1. Token Support

  2. Pool Management

System Architecture

graph TD subgraph Users A1[Regular Users] A2[Liquidity Providers] end subgraph Roles R1[Admin] R2[Operators] R3[Emergency Admins] end subgraph Core Contracts B[SimpleAMMV2] C[EternalStorage] D[EmergencyMultiSig] end subgraph LP System F[LP Tokens] end subgraph Libraries L[EternalStorageAccessLibrary] style L fill:#f9f,stroke:#333,stroke-width:2px end %% User interactions A1 --> B A2 --> B %% Role permissions R1 -->|Contract upgrades<br/>Role management| B R1 -->|Storage upgrades| C R2 -->|Fee adjustments| B R3 -->|Pause/Unpause<br/>Emergency actions| B R3 -->|Approve withdrawals| D %% Contract interactions B -->|Use| L L -->|Store/Read data| C B -->|Emergency withdrawals| D B -->|Create & Manage| F D -->|Execute| B

Role Permissions:

  1. Admin (DEFAULT_ADMIN_ROLE)

  2. Operators (OPERATOR_ROLE)

  3. Emergency Admins (EMERGENCY_ROLE)

Contract Interactions:

  1. SimpleAMMV2

  2. EternalStorage

  3. EmergencyMultiSig

  4. LPToken System

Design Rationale

1. Upgradability Pattern

The system uses the eternal storage pattern for upgradability:

2. Security Features

Multiple security layers are implemented:

3. AMM Design

The AMM implements a constant product formula with several optimizations:

User Guide

For Liquidity Providers

  1. Adding Liquidity

    function addLiquidity(address tokenAddress, uint256 tokenAmount) external payable returns (uint256 shares)
    
  2. Removing Liquidity

    function removeLiquidity(
        address tokenAddress,
        uint256 shares,
        uint256 minEthOut,
        uint256 minTokensOut,
        uint256 deadline
    )
    

For Traders

  1. ETH to Token Swaps

    function swapETHForTokens(
        address tokenAddress,
        uint256 minTokensOut,
        uint256 maxSlippage,
        uint256 deadline
    )
    
  2. Token to ETH Swaps

    function swapTokensForETH(
        address tokenAddress,
        uint256 tokenAmount,
        uint256 minEthOut,
        uint256 maxSlippage,
        uint256 deadline
    )
    

Developer Guide

Contract Deployment

  1. Deploy Storage

    forge script script/Deploy.s.sol:DeployScript --rpc-url $RPC_URL --broadcast
    
  2. Upgrade & Migration

Integration Guide

  1. Pool Integration

    // Get pool information
    (uint256 tokenReserve, uint256 ethReserve, uint256 totalShares) = amm.getPoolInfo(tokenAddress);
    
    // Get spot price
    uint256 price = amm.getSpotPrice(tokenAddress);
    
    // Get swap information
    (uint256 amountOut, uint256 priceImpact) = amm.getSwapInfo(tokenAddress, amountIn, isEthIn);
    
  2. Event Monitoring

    event LiquidityAdded(address indexed provider, address indexed token, uint256 tokenAmount, uint256 ethAmount, uint256 shares);
    event LiquidityRemoved(address indexed provider, address indexed token, uint256 tokenAmount, uint256 ethAmount, uint256 shares);
    event TokenSwap(address indexed token, uint256 tokenAmount, uint256 ethAmount);
    

Emergency Procedures

  1. Pausing the System

    // Only EMERGENCY_ROLE
    amm.pause();
    
  2. Emergency Withdrawal

    // Requires multi-sig approval
    multiSig.proposeWithdrawal(token, recipient, amount);
    multiSig.approveWithdrawal(proposalId);
    multiSig.executeWithdrawal(proposalId);
    
  3. Rollback Procedure

    forge script script/migration/Rollback.s.sol:RollbackScript --rpc-url $RPC_URL --broadcast
    

Out of Scope Features

Contract Security

Tests

Note: Due to limited time, only important tests are implemented. The coverage is not 100%.

forge test
Click to see test results
Ran 15 tests for test/SimpleAMMV2.t.sol:SimpleAMMV2Test
[PASS] test_AddLiquidity() (gas: 798965)
[PASS] test_EmergencyPause() (gas: 49071)
[PASS] test_EmergencyWithdrawal() (gas: 1252414)
[PASS] test_GetSpotPrice() (gas: 789345)
[PASS] test_GetSwapInfo() (gas: 793615)
[PASS] test_GetVersion() (gas: 9490)
[PASS] test_InitialSetup() (gas: 47007)
[PASS] test_RemoveLiquidity() (gas: 883108)
[PASS] test_RevertIf_EmergencyWithdrawal_NotEnoughApprovals() (gas: 933197)
[PASS] test_RevertWhenDeadlineExpired() (gas: 16686)
[PASS] test_Slippage() (gas: 802895)
[PASS] test_SupportMultipleTokens() (gas: 2071402)
[PASS] test_SwapETHForTokens() (gas: 821940)
[PASS] test_SwapTokensForETH() (gas: 826020)
[PASS] test_Unpause() (gas: 800903)
Suite result: ok. 15 passed; 0 failed; 0 skipped; finished in 9.86ms (4.28ms CPU time)

Ran 26 tests for test/storage/EternalStorage.t.sol:EternalStorageTest
[PASS] testAddressStorage() (gas: 28744)
[PASS] testAddressStorageUnauthorized() (gas: 15189)
[PASS] testBoolStorage() (gas: 27480)
[PASS] testBoolStorageUnauthorized() (gas: 13624)
[PASS] testBytesStorage() (gas: 84754)
[PASS] testBytesStorageUnauthorized() (gas: 14294)
[PASS] testConstructor() (gas: 17881)
[PASS] testConstructorZeroAddressReverts() (gas: 38928)
[PASS] testFuzz_AddressStorage(bytes32,address) (runs: 256, μ: 37487, ~: 37487)
[PASS] testFuzz_BoolStorage(bytes32,bool) (runs: 256, μ: 27436, ~: 36998)
[PASS] testFuzz_BytesStorage(bytes32,bytes) (runs: 256, μ: 71206, ~: 61925)
[PASS] testFuzz_IntStorage(bytes32,int256) (runs: 256, μ: 36577, ~: 36733)
[PASS] testFuzz_StringStorage(bytes32,string) (runs: 256, μ: 58663, ~: 39589)
[PASS] testFuzz_UintStorage(bytes32,uint256) (runs: 256, μ: 36343, ~: 36732)
[PASS] testIntStorage() (gas: 27203)
[PASS] testIntStorageUnauthorized() (gas: 13562)
[PASS] testMultipleStorageTypes() (gas: 112992)
[PASS] testPreviousLogicContractCannotCallStorageFunctions() (gas: 25768)
[PASS] testStressStorage() (gas: 11046842)
[PASS] testStringStorage() (gas: 30608)
[PASS] testStringStorageUnauthorized() (gas: 13869)
[PASS] testUintStorage() (gas: 27306)
[PASS] testUintStorageUnauthorized() (gas: 13601)
[PASS] testUpgradeLogicContract() (gas: 27033)
[PASS] testUpgradeLogicContractUnauthorized() (gas: 15306)
[PASS] testUpgradeLogicContractZeroAddressReverts() (gas: 13879)
Suite result: ok. 26 passed; 0 failed; 0 skipped; finished in 62.12ms (56.57ms CPU time)

Ran 22 tests for test/fuzz/SimpleAMMV2.fuzz.t.sol:SimpleAMMV2FuzzTest
[PASS] testFuzz_AddLiquidity(uint256,uint256) (runs: 256, μ: 803193, ~: 803305)
[PASS] testFuzz_RemoveLiquidity(uint256,uint256,uint256) (runs: 256, μ: 826765, ~: 827200)
[PASS] testFuzz_RevertIf_PriceImpactTooHigh_SwapETHForTokens(uint256) (runs: 256, μ: 806738, ~: 806679)
[PASS] testFuzz_RevertIf_PriceImpactTooHigh_SwapTokensForETH(uint256) (runs: 256, μ: 800138, ~: 800065)
[PASS] testFuzz_SpotPrice(uint256,uint256) (runs: 256, μ: 794425, ~: 794531)
[PASS] testFuzz_SwapETHForTokens(uint256) (runs: 256, μ: 822614, ~: 822660)
[PASS] testFuzz_SwapTokensForETH(uint256) (runs: 256, μ: 829300, ~: 829355)
[PASS] test_AddLiquidity() (gas: 799086)
[PASS] test_EmergencyPause() (gas: 49004)
[PASS] test_EmergencyWithdrawal() (gas: 1252414)
[PASS] test_GetSpotPrice() (gas: 789411)
[PASS] test_GetSwapInfo() (gas: 793670)
[PASS] test_GetVersion() (gas: 9490)
[PASS] test_InitialSetup() (gas: 47051)
[PASS] test_RemoveLiquidity() (gas: 883152)
[PASS] test_RevertIf_EmergencyWithdrawal_NotEnoughApprovals() (gas: 933197)
[PASS] test_RevertWhenDeadlineExpired() (gas: 16597)
[PASS] test_Slippage() (gas: 802917)
[PASS] test_SupportMultipleTokens() (gas: 2071358)
[PASS] test_SwapETHForTokens() (gas: 821940)
[PASS] test_SwapTokensForETH() (gas: 826020)
[PASS] test_Unpause() (gas: 800836)
Suite result: ok. 22 passed; 0 failed; 0 skipped; finished in 19.80s (417.57ms CPU time)

Ran 11 tests for test/EmergercyMultiSig.t.sol:EmergencyMultiSigTest
[PASS] test_ApproveWithdrawal() (gas: 190381)
[PASS] test_Constructor() (gas: 37323)
[PASS] test_ExecuteWithdrawal() (gas: 225745)
[PASS] test_ProposeWithdrawal() (gas: 163059)
[PASS] test_RevertIf_ApprovalAfterProposal() (gas: 159587)
[PASS] test_RevertIf_ApproveWithdrawalTwice() (gas: 190491)
[PASS] test_RevertIf_ExecuteWithoutSufficientApprovals() (gas: 164210)
[PASS] test_RevertIf_InvalidAmount() (gas: 19230)
[PASS] test_RevertIf_InvalidRecipient() (gas: 19210)
[PASS] test_RevertIf_NonSignerProposal() (gas: 15729)
[PASS] test_RevertIf_ProposalExpired() (gas: 165994)
Suite result: ok. 11 passed; 0 failed; 0 skipped; finished in 19.80s (1.01ms CPU time)

Ran 6 tests for test/storage/EternalStorageAccessLibrary.t.sol:EternalStorageAccessLibraryTest
[PASS] test_EmergencyMultiSig() (gas: 38685)
[PASS] test_FeeManagement() (gas: 37004)
[PASS] test_FeeManagementUnauthorized() (gas: 13461)
[PASS] test_PoolOperations() (gas: 615744)
[PASS] test_PoolOperationsUnauthorized_EthReserve() (gas: 15870)
[PASS] test_PoolOperationsUnauthorized_TokenReserve() (gas: 15805)
Suite result: ok. 6 passed; 0 failed; 0 skipped; finished in 19.80s (3.22ms CPU time)

Ran 4 tests for test/invariant/LiquidityInvariant.t.sol:LiquidityInvariantTest
[PASS] invariant_LiquidityRatios() (runs: 256, calls: 128000, reverts: 0)
[PASS] invariant_MinimumLiquidity() (runs: 256, calls: 128000, reverts: 0)
[PASS] invariant_ReserveRatio() (runs: 256, calls: 128000, reverts: 0)
[PASS] invariant_ReservesNotZero() (runs: 256, calls: 128000, reverts: 0)
Suite result: ok. 4 passed; 0 failed; 0 skipped; finished in 21.06s (79.01s CPU time)

Ran 2 tests for test/invariant/SimpleAMMV2.invariant.t.sol:SimpleAMMV2InvariantTest
[PASS] invariant_PausedStateConsistency() (runs: 256, calls: 128000, reverts: 0)
[PASS] invariant_RolesAndPermissions() (runs: 256, calls: 128000, reverts: 0)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 21.37s (42.03s CPU time)

Ran 2 tests for test/invariant/SwapInvariant.t.sol:SwapInvariantTest
[PASS] invariant_SwapConstantProduct() (runs: 256, calls: 128000, reverts: 0)
[PASS] invariant_callSummary() (runs: 256, calls: 128000, reverts: 0)
Suite result: ok. 2 passed; 0 failed; 0 skipped; finished in 22.82s (42.30s CPU time)

Ran 8 test suites in 22.83s (124.73s CPU time): 88 tests passed, 0 failed, 0 skipped (88 total tests)

Gas Optimization

Key Optimization Techniques

  1. Storage Access Optimization

    // Before
    if (store.getPoolLPTokenWithAddress(tokenAddress) == address(0)) {
        // do something
    }
    
    // After
    address lpTokenAddr = store.getPoolLPTokenWithAddress(tokenAddress);
    if (lpTokenAddr == address(0)) {
        // do something
    }
    
  2. Computation Optimization

    // Before
    uint256 tokensAmountBasedOnEth = (ethAmount * tokenReserve) / ethReserve;
    
    // After
    unchecked {
        uint256 tokensAmountBasedOnEth = (ethAmount * tokenReserve) / ethReserve;
    }
    
  3. Gas-Efficient Patterns

    // Before
    require(msg.value > 0, "Zero ETH amount");
    
    // After
    if (msg.value == 0) revert SimpleAMM__ZeroETHAmount();
    
  4. Memory Management

    // Before
    function getPoolInfo() returns (uint256, uint256, uint256) {
        uint256 tokenReserve = store.getPoolTokenReservesWithAddress(token);
        uint256 ethReserve = store.getPoolEthReservesWithAddress(token);
        uint256 totalShares = lpToken.totalSupply();
        return (tokenReserve, ethReserve, totalShares);
    }
    
    // After
    function getPoolInfo() returns (uint256 tokenReserve, uint256 ethReserve, uint256 totalShares) {
        (tokenReserve, ethReserve, address lpTokenAddr) = store.getPoolWithAddress(token);
        totalShares = LPToken(lpTokenAddr).totalSupply();
    }
    
  5. Event Optimization

  6. Reentrancy Protection

    // Transient storage costs less gas than permanent storage
    contract SimpleAMMV2 is ReentrancyGuardTransient {
        function withdraw() external nonReentrant {
            // Protected against reentrancy
        }
    }
    
  7. Constant and Immutable Variables

Gas Savings Breakdown

Operation Gas Saved
SLOAD optimization ~2100
SSTORE optimization ~5000
Memory vs Storage ~2000
Unchecked arithmetic ~35
Custom errors ~50
ReentrancyGuardTransient ~15000

Function-Specific Optimizations

  1. addLiquidity

  2. swapETHForTokens

  3. removeLiquidity

Considerations

  1. Trade-offs